Introduction
A CAPTCHA is a program that can generate and grade tests that:
- Most humans can pass, BUT
- Current computer programs can't pass
CAPTCHA stands for "Completely Automated Public Turing test to Tell Computers and Humans Apart". See the CAPTCHA Site for more details. The concept of a CAPTCHA is motivated by a real-world problem: how to prevent "bots" automatic registrations? By requiring the user to solve a CAPTCHA, the "bots" could be screened out.
WSCaptcha is a web service that creates CAPTCHA images. To see how web service really works please visit my site.
Inside the service
To obtain a little twisted images I applied a swirl and a water filter. These filters are realized using code written by Christian Graus in his Filter class (Filter.cs).
WSCaptcha's core is Captcha
class. This class expose the following properties to set main image's characteristic.
public int Width
{
set
{
img_width = value;
}
}
public int Height
{
set
{
img_height = value;
}
}
public string FontName
{
set
{
img_fontname = value;
}
}
public float FontSize
{
set
{
img_fontsize = value;
}
}
public string Keyword
{
set
{
sKeyword = value;
}
}
Captcha
class also exposes the following method to create the CAPTCHA image.
public Bitmap makeCaptcha()
{
Random randomGenerator = new Random();
Bitmap bmp = new Bitmap(img_width, img_height,
PixelFormat.Format16bppRgb555);
Rectangle rect = new Rectangle(0, 0, img_width, img_height);
StringFormat sFormat = new StringFormat();
sFormat.Alignment = StringAlignment.Center;
sFormat.LineAlignment = StringAlignment.Center;
Graphics g = Graphics.FromImage(bmp);
SizeF size;
float fontSize = img_fontsize + 1;
Font font;
try
{
font = new Font(img_fontname, img_fontsize);
font.Dispose();
}
catch (Exception ex)
{
img_fontname = System.Drawing.FontFamily.GenericSerif.Name;
}
string tempKey = "";
for(int ii=0; ii< sKeyword.Length; ii++)
{
tempKey = String.Concat(tempKey, sKeyword[ii].ToString());
tempKey = String.Concat(tempKey, " ");
}
do
{
fontSize--;
font = new Font(img_fontname, fontSize, img_fontstyle);
size = g.MeasureString(tempKey, font);
} while (size.Width > (0.8*bmp.Width));
img_font = font;
g.Clear(Color.Silver);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.FillRectangle(
new LinearGradientBrush(
new Point(0,0),
new Point(bmp.Width,bmp.Height),
Color.FromArgb(
255,
randomGenerator.Next(255),
randomGenerator.Next(255),
randomGenerator.Next(255)
),
Color.FromArgb(
randomGenerator.Next(100),
randomGenerator.Next(255),
randomGenerator.Next(255),
randomGenerator.Next(255)
) ),
rect);
if ( fDegree == 0)
{
BitmapFilter.Swirl(bmp, randomGenerator.NextDouble(), true);
}
else
{
BitmapFilter.Swirl(bmp, fDegree , true);
}
HatchBrush hatchBrush = new HatchBrush(
HatchStyle.LargeConfetti,
Color.LightGray,
Color.DarkGray);
int m = Math.Max(rect.Width, rect.Height);
for (int i = 0; i < (int) (rect.Width * rect.Height / 30F); i++)
{
int x = randomGenerator.Next(rect.Width);
int y = randomGenerator.Next(rect.Height);
int w = randomGenerator.Next(m / 50);
int h = randomGenerator.Next(m / 50);
g.FillEllipse(hatchBrush, x, y, w, h);
}
int posx;
int posy;
int deltax;
deltax = Convert.ToInt32(size.Width/tempKey.Length);
posx = Convert.ToInt32((img_width - size.Width)/2);
for (int l=0; l < tempKey.Length; l++ )
{
posy = ((int)(2.5 * (bmp.Height/5))) +
(((l%2)==0)?-2:2) * ((int)(size.Height/3));
posy = (int)((bmp.Height/2)+ (size.Height/2));
posy += (int) ((((l%2)==0)?-2:2) * ((size.Height/3)));
posx += deltax;
g.DrawString(tempKey[l].ToString(),
img_font,
img_fontcolor,
posx,
posy,
sFormat);
}
Point[] ps = new Point[nPoints];
for (int ii=0; ii < nPoints; ii++)
{
int x,y;
x = randomGenerator.Next(bmp.Width);
y = randomGenerator.Next(bmp.Height);
ps[ii] = new Point(x,y);
}
g.DrawCurve(new Pen( img_fontcolor, 2), ps,
Convert.ToSingle(randomGenerator.NextDouble()));
BitmapFilter.Water(bmp, nWave, false);
font.Dispose();
hatchBrush.Dispose();
g.Dispose();
return bmp;
}
Please remember that, to make available an image via a web service, it's necessary to send the image's byte data. So, to do that we have to use a stream as you can see in the following lines:
.......
Bitmap bmp = cc.makeCaptcha();
MemoryStream ms = new MemoryStream();
bmp.Save(ms,ImageFormat.Jpeg);
ImageBytes = ms.GetBuffer();
return ImageBytes;
.......
Using the web service
WSCaptcha web service exposes three web methods.
The first one to create an image specifying size and keyword but using default font and size.
public byte[] CaptchaImage2(int nWidth, int nHeight, string sKeyword)
{
Captcha.Captcha cc = new Captcha.Captcha();
cc.Width = nWidth;
cc.Height = nHeight;
cc.Keyword = sKeyword;
Bitmap bmp = cc.makeCaptcha();
MemoryStream ms = new MemoryStream();
bmp.Save(ms,ImageFormat.Jpeg);
ImageBytes = ms.GetBuffer();
return ImageBytes;
}
The second one to create an image specifying all the parameters.
public byte[] CaptchaImage1(int nWidth, int nHeight,
string sKeyword, string sFontName, float fFontSize )
{
Captcha.Captcha cc = new Captcha.Captcha();
cc.Width = nWidth;
cc.Height = nHeight;
cc.FontName = sFontName;
cc.FontSize = fFontSize;
cc.Keyword = sKeyword;
Bitmap bmp = cc.makeCaptcha();
MemoryStream ms = new MemoryStream();
bmp.Save(ms,ImageFormat.Jpeg);
ImageBytes = ms.GetBuffer();
return ImageBytes;
}
The third one to retrieve a list of available fonts, used to write keyword on the image.
public string GetFontNames()
{
string result = "";
FontFamily[] fntfamily =
FontFamily.Families; for
(int i =
0;i < fntfamily.Length; i++)
{
result +="<fontname>" + fntfamily[i].Name.ToString() + "</fontname>";
}
return result;
}
To use this web service you can use the proxy class downloadable by the top link or you can create your proxy class using the following WSDL
utility command line:
C:\>wsdl /language:C# /out:WSCaptchaProxy.cs
http://www.code4dotnet.com/WSCaptcha/CaptchaService.asmx
How to build a registration process using CAPTCHA verification code?
Certified registration data are of vital importance to be sure that the data you are receiving is coming from an actual human being. Simply adding in your registration form an image containing the CAPTCHA code and a keyword field, you are sure enough that a human has filled your form. Your user has to fill the keyword field with the code inside the CAPTCHA image.
The registration form must contain code both to generate the image and to check the keyword. The CAPTCHA image will be generated using an HttpHandler
so that there will not be physical image files to store. To use the HttpHandler
add the following rows in the <system.web>
section of Web.config
<httpHandlers>
<add verb="*" path="GetCapcthaImage.aspx"
type="CaptchaHttpHandler.CaptchaImgHttpHandler, capcthahttphandler"/>
</httpHandlers>
This handler defines a page named GetCaptchaImage.aspx that doesn't actually exist. Every requests for GetCaptchaImage.aspx will be intercepted and handled by the CaptchaImgHttpHandler
which implements the IHttpHandler
interface. This interface has the following members:
ProcessRequest()
� this method is invoked by the runtime to handle the request.
IsReusable
� this property indicates if multiple requests can share the same handler.
The following code shows the ProcessRequest
method:
private double dTimeout = 120;
private int keylen = 7;
private int width = 350;
private int height = 200;
public void ProcessRequest(HttpContext context)
{
string sGuid = context.Request.QueryString["guid"].ToString();
string mapcar="123456789abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ";
Random randomGenerator = new Random();
string keyword = "";
for (int i = 0; i < keylen; i++)
{
keyword += mapcar.Substring(randomGenerator.Next(mapcar.Length),1);
}
System.Byte[] imgCaptcha;
string fontname = "Lucida Console";
CaptchaService CaptchaServ = new CaptchaService();
imgCaptcha = CaptchaServ.CaptchaImage1(width, height, keyword.ToString(),
fontname.ToString(), Convert.ToSingle(45));
MemoryStream memStream = new MemoryStream(imgCaptcha);
Bitmap Image=new Bitmap(memStream);
HttpContext.Current.Cache.Insert(sGuid, keyword, null,
DateTime.Now.AddSeconds(dTimeout),
TimeSpan.Zero, CacheItemPriority.High, null);
Image.Save(context.Response.OutputStream,
System.Drawing.Imaging.ImageFormat.Jpeg);
context.Response.ContentType = "image/jpeg";
context.Response.StatusCode = 200;
context.Response.End();
}
I use a GUID identifier to store and to access keyword in the ASP.NET cache and so the registration form must contain an image tag like this:
<img id="Image1"
src="GetCapcthaImage.aspx?guid=72fa6e45-6dfa-46a7-8343-72d56a14f953"
alt="" border="0" />
It is automatically obtained by a simple code like this:
string sGuid = Guid.NewGuid().ToString();
Image1.ImageUrl = "GetCapcthaImage.aspx?guid=" + sGuid;
When data is posted from the registration form we have to proceed as follows:
- Check if the Captcha keyword stored in the cache is still valid or expired.
- Match the user keyword with the Captcha one.
That's all.
Note
Demo project contains: the web service proxy class (WSCaptchaProxy.cs), an ASP.NET demo site and the project that implements the HttpHandler
.
For installation information please refer to the readme file that you can find inside the ZIP file.
Conclusion
Many thanks to BrainJar and to wumpus1 for their articles about CAPTCHA argument.
I hope you enjoyed this article.